﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections;
using System.Numerics;
using System.IO;

namespace eVoting
{
    class Pailler
    {
        private BigInteger p, q, n2, n, g, lambda, phi, mu;
        private BigInteger Nv, Nc;
        private BigInteger b;
        private BigInteger mmax;
        private BigInteger tmax;
        private ArrayList random;
        private Random rand;
        private int pollID;

        //static void Main(string[] args)
        //{
        //    Pailler pailler = new Pailler(50, 3);
        //    pailler.generateKeys();
        //    pailler.WriteToFile(2);

        //    List<BigInteger> votes = new List<BigInteger>();
        //    Pailler pailler2 = new Pailler(2);
        //    votes.Add(pailler2.encrypt(1));
        //    Pailler pailler3 = new Pailler(2);
        //    votes.Add(pailler3.encrypt(2));
        //    Pailler pailler4 = new Pailler(2);
        //    votes.Add(pailler4.encrypt(2));
        //    Pailler pailler5 = new Pailler(2);
        //    votes.Add(pailler5.encrypt(3));
        //    Pailler pailler6 = new Pailler(2);
        //    votes.Add(pailler6.encrypt(3));
        //    Pailler pailler7 = new Pailler(2);
        //    votes.Add(pailler7.encrypt(3));

        //    Pailler pailler8 = new Pailler(2);
        //    pailler8.decrypt(pailler8.tally(votes));
        //}

        public Pailler(int pollID)
        {
            TextReader tr = new StreamReader("Polls\\Poll" + pollID + ".txt");

            String values = tr.ReadLine();

            BigInteger[] n = new BigInteger[7];

            String[] numbers = values.Split(',');

            for (int i = 0; i < numbers.Length; i++)
            {
                n[i] = BigInteger.Parse(numbers[i]);
            }

            this.b = n[0];
            this.n = n[1];
            this.n2 = n[2];
            this.g = n[3];
            this.lambda = n[4];
            this.mu = n[5];
            this.Nv = n[6];
            this.random = new ArrayList();
            this.rand = new Random();
            this.pollID = pollID;

            tr.Close();
        }

        public Pailler(int Nv, int Nc)
        {
            this.Nv = new BigInteger(Nv);
            this.Nc = new BigInteger(Nc);
            this.p = new BigInteger();
            this.q = new BigInteger();
            this.n2 = new BigInteger();
            this.n = new BigInteger();
            this.g = new BigInteger();
            this.lambda = new BigInteger();
            this.phi = new BigInteger();
            this.mu = new BigInteger();
            this.rand = new Random();
            this.b = Nv + 1;
            this.mmax = BigInteger.Pow(b, (Nc - 1));
            this.tmax = BigInteger.Multiply(this.Nv, mmax);
            this.pollID = 0;
        }

        public void generateKeys()
        {
            bool flag = true;

            while (flag)
            {
                p = new BigInteger(rand.Next(999));
                q = new BigInteger(rand.Next(999));
                int tmaxsqrt = (int)Math.Sqrt((int)tmax);

                if (IsPrime(p) && IsPrime(q) && (p != q) && (p.ToString().Length == q.ToString().Length) &&
                    p > tmaxsqrt && q > tmaxsqrt)
                {
                    if (BigInteger.GreatestCommonDivisor((p * q), ((p - 1) * (q - 1))) == 1)
                    {
                        if ((p * q) >= tmax + 1)
                        {
                            flag = false;
                        }
                    }
                }
            }

            n = p * q;
            n2 = BigInteger.Pow(n, 2);
            lambda = ((p - 1) * (q - 1)) / BigInteger.GreatestCommonDivisor(p - 1, q - 1);
            phi = (p - 1) * (q - 1);

            flag = true;
            while (flag)
            {
                g = new BigInteger(rand.Next(100));
                Console.Write(g + "\n");
                ;
                if (isCoprime(g, n2))
                {
                    if (BigInteger.GreatestCommonDivisor(BigInteger.ModPow(g, lambda, (n2 - 1)) / n, n) == 1)
                        flag = false;
                }
            }

            mu = modInverse(BigInteger.Divide(BigInteger.Subtract(BigInteger.ModPow(g, lambda, n2), 1), n), n);

            if (mu < 0)
                generateKeys();
        }

        public void WriteToFile(int PollId)
        {
            Directory.CreateDirectory("Polls");
            
            TextWriter tw = new StreamWriter("Polls\\Poll" + PollId + ".txt");

            tw.WriteLine(this.b + "," + this.n + "," + this.n2 + "," + this.g + "," + this.lambda + "," + this.mu + "," + this.Nv);

            tw.Close();
        }

        public BigInteger encrypt(int message)
        {
            BigInteger m = BigInteger.Pow(b, message - 1);
            BigInteger c = 0;
            bool flag = true;

            if (File.Exists("Polls\\PollRandom" + pollID + ".txt"))
            {
                TextReader tr = new StreamReader("Polls\\PollRandom" + pollID + ".txt");

                String values = tr.ReadLine();

                BigInteger[] randomNumbers = new BigInteger[int.Parse(Nv.ToString())];

                String[] numbers = values.Split(',');

                for (int i = 0; i < numbers.Length; i++)
                {
                    random.Add(BigInteger.Parse(numbers[i]));
                }

                tr.Close();
            }

            while (flag)
            {
                if (isCoprime(m, n))
                {
                    BigInteger r = rand.Next(2, int.Parse(Nv.ToString()) + 50);

                    if (isCoprime(r, n2) && !random.Contains(r))
                    {
                        BigInteger gpowerm = BigInteger.Pow(g, int.Parse(m.ToString())); 
                        BigInteger rpowern = BigInteger.Pow(r, int.Parse(n.ToString()));
                        c = BigInteger.Multiply(gpowerm, rpowern) % n2;

                        random.Add(r);
                        Directory.CreateDirectory("Polls");
                        TextWriter tw = new StreamWriter("Polls\\PollRandom" + pollID + ".txt");
                        for (int i = 0; i < random.Count; i++) 
                        {
                            if(i == random.Count - 1)
                                tw.WriteLine(random[i]);
                            else
                                tw.WriteLine(random[i]);
                        }
                        
                        tw.Close();

                        Console.WriteLine("c: " + c);
                        flag = false;
                    }
                }
            }

            return c;
        }

        public List<int> decrypt(BigInteger ciphertext)
        {
            BigInteger m = 0;

            if (isCoprime(ciphertext, n2))
            {
                m = BigInteger.Multiply(BigInteger.Divide(BigInteger.Subtract(BigInteger.ModPow(ciphertext, lambda, n2), 1), n), mu) % n;
            }

            String s = DecimalToBase(int.Parse(m.ToString()), int.Parse(b.ToString()));

            List<int> results = new List<int>();

            for (int i = 0; i < s.Length; i++)
            {
                results.Add(BaseToDecimal(s[i] + "", int.Parse(b.ToString())));
            }

            return results;
        }

        public BigInteger tally(List<BigInteger> encryptedVotes)
        {
            BigInteger product = BigInteger.One;

            foreach (BigInteger i in encryptedVotes)
            {
                product *= i;
            }

            return product % n2;
        }

        private BigInteger GetGCDByModulus(BigInteger value1, BigInteger value2)
        {
            while (value1 != 0 && value2 != 0)
            {
                if (value1 > value2)
                    value1 %= value2;
                else
                    value2 %= value1;
            }
            return BigInteger.Max(value1, value2);
        }

        private bool isCoprime(BigInteger value1, BigInteger value2)
        {
            return GetGCDByModulus(value1, value2) == 1;
        }

        public static bool IsPrime(BigInteger candidate)
        {
            if ((BigInteger.Parse(candidate + "") & 1) == 0)
            {
                if (candidate == 2)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            for (ulong i = 3; (i * i) <= candidate; i += 2)
            {
                if ((candidate % i) == 0)
                {
                    return false;
                }
            }
            return candidate != 1;
        }

        private BigInteger modInverse(BigInteger a, BigInteger b)
        {
            //trivial case: b = 0 => a^-1 = 1
            if (b.Equals(BigInteger.Zero))
            {
                return BigInteger.One;
            }
            //all other cases
            BigInteger x2 = BigInteger.One;
            BigInteger x1 = BigInteger.Zero;
            BigInteger y2 = BigInteger.Zero;
            BigInteger y1 = BigInteger.One;
            BigInteger x, y, q, r;
            while (b.CompareTo(BigInteger.Zero) == 1)
            {
                q = BigInteger.Divide(a, b);
                //q = a.divide(b);
                r = BigInteger.Subtract(a, BigInteger.Multiply(q, b));
                //r = a.subtract(q.multiply(b));
                x = BigInteger.Subtract(x2, BigInteger.Multiply(q, x1));
                //x = x2.subtract(q.multiply(x1));
                y = BigInteger.Subtract(y2, BigInteger.Multiply(q, y1));
                //y = y2.subtract(q.multiply(y1));
                a = b;
                b = r;
                x2 = x1;
                x1 = x;
                y2 = y1;
                y1 = y;
            }
            if (!a.Equals(BigInteger.One))
                Console.Write("a and n are not coprime");
            return x2;
        }

        public static string DecimalToBase(int iDec, int numbase)
        {
            const int base10 = 10;
            char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
            string strBin = "";
            int[] result = new int[32];
            int MaxBit = 32;
            for (; iDec > 0; iDec /= numbase)
            {
                int rem = iDec % numbase;
                result[--MaxBit] = rem;
            }
            for (int i = 0; i < result.Length; i++)
                if ((int)result.GetValue(i) >= base10)
                    strBin += cHexa[(int)result.GetValue(i) % base10];
                else
                    strBin += result.GetValue(i);
            strBin = strBin.TrimStart(new char[] { '0' });
            return strBin;
        }

        public static int BaseToDecimal(string sBase, int numbase)
        {
            const int base10 = 10;
            char[] cHexa = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
            int[] iHexaNumeric = new int[] { 10, 11, 12, 13, 14, 15 };
            const int asciiDiff = 48;
            int[] result = new int[32];
            int dec = 0;
            int b;
            int iProduct = 1;
            string sHexa = "";
            if (numbase > base10)
                for (int i = 0; i < cHexa.Length; i++)
                    sHexa += cHexa.GetValue(i).ToString();
            for (int i = sBase.Length - 1; i >= 0; i--, iProduct *= numbase)
            {
                string sValue = sBase[i].ToString();
                if (sValue.IndexOfAny(cHexa) >= 0)
                    b = iHexaNumeric[sHexa.IndexOf(sBase[i])];
                else
                    b = (int)sBase[i] - asciiDiff;
                dec += (b * iProduct);
            }
            return dec;
        }
    }
}